﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Globalization;

namespace Dillon.CSV
{
    class CSVLexer : ILexer
    {
        //const int MAX_IDENTIFIER_LENGTH = 512;
        //char[] identBuffer = new char[MAX_IDENTIFIER_LENGTH];

        LexerReader reader;
        int _col = 1;
        int _line = 1;
        Token _currentToken = null;
        Token _nextToken = null;
        readonly CSVReaderSettings settings;

        public CSVLexer(TextReader reader, CSVReaderSettings settings)
        {
            this.reader = new LexerReader(reader);
            this.settings = settings;

        }


        protected int Line
        {
            get
            {
                return _line;
            }
        }

        protected int Col
        {
            get
            {
                return _col;
            }
        }

        int _columnIndex;
        int _rowIndex;

        protected virtual Token GetNextTokenCore()
        {
            char ch;

            while (true)
            {
                Location startLocation = new Location(Col, Line);
                int nextChar = ReaderRead();
                if (nextChar == -1)
                {
                    break;
                }

                Token token = null;

                if (this.IsColumnSeparator((char)nextChar))
                {
                    token = new Token(Tokens.ColumnSymbol, Col, Line, this._columnIndex, this._rowIndex);
                    this._columnIndex += 1;
                }
                else if (nextChar == '\r' || nextChar == '\n')
                {
                    token = new Token(Tokens.RowSymbol, Col, Line, this._columnIndex, this._rowIndex);
                    HandleLineEnd((char)nextChar);
                    this._rowIndex += 1;
                    this._columnIndex = 0;
                }
                else if (nextChar == this.settings.StringSymbol)
                {
                    token = ReadString();
                }
                else if (nextChar == '\0')
                {
                    return new Token(Tokens.EOF, Col, Line, this._columnIndex, this._rowIndex, String.Empty);
                }
                else
                {
                    ch = (char)nextChar;
                    int x = Col - 1;
                    int y = Line;
                    string s = ReadCellValue(ch);
                    token = new Token(Tokens.NormalCellValue, x, y, this._columnIndex, this._rowIndex, s);
                }


                if (token != null)
                {
                    return token;
                }
                else
                {
                    this.Errors.Error(startLocation.Line, startLocation.Column, "Invalid character");
                }
            }

            return new Token(Tokens.EOF, Col, Line, this._columnIndex, this._rowIndex, String.Empty);
        }




        protected int ReaderRead()
        {
            int val = reader.Read();
            if ((val == '\r' && reader.Peek() != '\n') || val == '\n')
            {
                ++_line;
                _col = 1;
            }
            else if (val >= 0)
            {
                _col++;
            }
            return val;
        }



        protected StringBuilder realStringValue = new StringBuilder();
        protected StringBuilder originalValue = new StringBuilder();

        Token ReadString()
        {
            int x = Col - 1;
            int y = Line;

            realStringValue.Length = 0;
            originalValue.Length = 0;
            originalValue.Append('"');
            //bool doneNormally = false;
            int nextChar;
            while ((nextChar = ReaderRead()) != -1)
            {
                char ch = (char)nextChar;

                if (ch == this.settings.StringSymbol)
                {
                    var pkChar = ReaderPeek();
                    if (pkChar != -1 && ((char)pkChar) == this.settings.StringSymbol)
                    {
                        realStringValue.Append(this.settings.StringSymbol);
                        originalValue.Append(this.settings.StringSymbol);
                        originalValue.Append(this.settings.StringSymbol);
                        ReaderRead();
                        continue;
                    }
                    else if (IsCellPart(pkChar))
                    {
                        //doneNormally = false;
                        originalValue.Append(this.settings.StringSymbol);
                        ReaderRead();
                        var s = ReadCellValue((char)pkChar);
                        originalValue.Append(s);
                        realStringValue.Append(s);
                        break;
                    }
                    else
                    {
                        //doneNormally = true;
                        originalValue.Append(this.settings.StringSymbol);
                        break;
                    }
                }
                else
                {
                    originalValue.Append(ch);
                    realStringValue.Append(ch);
                }
            }

            //if (!doneNormally)
            //{
            //    errors.Error(y, x, String.Format("End of file reached inside string literal"));
            //}

            return new Token(Tokens.StringCellValue, x, y, this._columnIndex
                , this._rowIndex, realStringValue.ToString(), originalValue.ToString());
        }

        protected bool HandleLineEnd(char ch)
        {
            if (ch == '\r')
            {
                if (reader.Peek() == '\n')
                {
                    ReaderRead();
                    return true;
                }
                else
                {
                    return true;
                }
            }
            if (ch == '\n')
            {
                return true;
            }
            return false;
        }



        protected Errors errors = new Errors();
        public Errors Errors
        {
            get
            {
                return errors;
            }
        }

        protected int ReaderPeek()
        {
            return reader.Peek();
        }

        string ReadCellValue(char ch)
        {
            int peek;
            //int curPos = 0;
            StringBuilder s = new StringBuilder();
            while (true)
            {

                if (ch != '\0') // only add character, if it is valid
                    // prevents \ from being added
                    s.Append(ch);

                peek = ReaderPeek();
                if (IsCellPart(peek))
                {
                    ch = (char)ReaderRead();
                }
                else
                {
                    break;
                }
            }
            return s.ToString();
        }

        bool IsCellPart(int ch)
        {
            if (ch == -1) return false;
            if (ch == '\r') return false;
            if (ch == '\n') return false;
            if (this.IsColumnSeparator((char)ch)) return false;
            return true;
        }

        bool IsColumnSeparator(char ch)
        {
            return ch == this.settings.ColumnSeparator;
        }




        public Token Token
        {
            get { return this._currentToken; }
        }

        public Token Next
        {
            get { return this._nextToken; }
        }


        public Token MoveToNextToken()
        {
            if (_nextToken == null)
            {
                _nextToken = GetNextTokenCore();
                return _nextToken;
            }

            _currentToken = _nextToken;
            if (_nextToken.next == null)
            {
                _nextToken.next = GetNextTokenCore();
            }

            _nextToken = _nextToken.next;
            return _nextToken;
        }


        public void Dispose()
        {
            this.reader.Dispose();
            this.reader = null;
        }
    }
}
